AsyncLocalStorage ഉപയോഗിച്ച് Node.js-ൽ റിക്വസ്റ്റ്-സ്കോപ്പ്ഡ് വേരിയബിൾ മാനേജ്മെൻ്റിൽ വൈദഗ്ദ്ധ്യം നേടുക. പ്രോപ്പ് ഡ്രില്ലിംഗ് ഒഴിവാക്കി ആഗോള ഉപയോക്താക്കൾക്കായി മികച്ചതും നിരീക്ഷിക്കാൻ എളുപ്പമുള്ളതുമായ ആപ്ലിക്കേഷനുകൾ നിർമ്മിക്കുക.
ജാവാസ്ക്രിപ്റ്റ് അസിങ്ക് കോൺടെക്സ്റ്റ് മനസ്സിലാക്കാം: റിക്വസ്റ്റ്-സ്കോപ്പ്ഡ് വേരിയബിൾ മാനേജ്മെൻ്റിനെക്കുറിച്ചുള്ള ആഴത്തിലുള്ള പഠനം
ആധുനിക സെർവർ-സൈഡ് ഡെവലപ്മെൻ്റിൻ്റെ ലോകത്ത്, സ്റ്റേറ്റ് മാനേജ്മെൻ്റ് ഒരു അടിസ്ഥാനപരമായ വെല്ലുവിളിയാണ്. Node.js-ൽ പ്രവർത്തിക്കുന്ന ഡെവലപ്പർമാർക്ക്, അതിൻ്റെ സിംഗിൾ-ത്രെഡഡ്, നോൺ-ബ്ലോക്കിംഗ്, അസിൻക്രണസ് സ്വഭാവം ഈ വെല്ലുവിളിയെ കൂടുതൽ വർദ്ധിപ്പിക്കുന്നു. ഉയർന്ന പ്രകടനമുള്ള, I/O-ബൗണ്ട് ആപ്ലിക്കേഷനുകൾ നിർമ്മിക്കുന്നതിന് ഈ മോഡൽ അവിശ്വസനീയമാംവിധം ശക്തമാണെങ്കിലും, ഇത് ഒരു സവിശേഷമായ പ്രശ്നം സൃഷ്ടിക്കുന്നു: ഒരു നിർദ്ദിഷ്ട അഭ്യർത്ഥനയുടെ (request) കോൺടെക്സ്റ്റ്, മിഡിൽവെയർ മുതൽ ഡാറ്റാബേസ് ക്വറികൾ, തേർഡ്-പാർട്ടി API കോളുകൾ വരെയുള്ള വിവിധ അസിൻക്രണസ് പ്രവർത്തനങ്ങളിലൂടെ കടന്നുപോകുമ്പോൾ നിങ്ങൾ എങ്ങനെ നിലനിർത്തും? ഒരു ഉപയോക്താവിൻ്റെ അഭ്യർത്ഥനയിൽ നിന്നുള്ള ഡാറ്റ മറ്റൊന്നിലേക്ക് ചോരുന്നില്ലെന്ന് നിങ്ങൾ എങ്ങനെ ഉറപ്പാക്കും?
വർഷങ്ങളായി, ജാവാസ്ക്രിപ്റ്റ് കമ്മ്യൂണിറ്റി ഈ പ്രശ്നവുമായി മല്ലിടുകയായിരുന്നു, പലപ്പോഴും "പ്രോപ്പ് ഡ്രില്ലിംഗ്" പോലുള്ള ബുദ്ധിമുട്ടുള്ള പാറ്റേണുകൾ അവലംബിച്ചു—ഒരു കോൾ ശൃംഖലയിലെ ഓരോ ഫംഗ്ഷനിലൂടെയും ഒരു യൂസർ ഐഡി അല്ലെങ്കിൽ ഒരു ട്രേസ് ഐഡി പോലുള്ള അഭ്യർത്ഥന-നിർദ്ദിഷ്ട ഡാറ്റ കൈമാറുക. ഈ സമീപനം കോഡ് സങ്കീർണ്ണമാക്കുകയും മൊഡ്യൂളുകൾക്കിടയിൽ കർശനമായ ബന്ധം സൃഷ്ടിക്കുകയും പരിപാലനം ഒരു പേടിസ്വപ്നമാക്കുകയും ചെയ്യുന്നു.
ഇവിടെയാണ് അസിങ്ക് കോൺടെക്സ്റ്റിൻ്റെ പ്രസക്തി. ഈ ദീർഘകാല പ്രശ്നത്തിന് ഇത് ശക്തമായ ഒരു പരിഹാരം നൽകുന്നു. Node.js-ൽ സ്ഥിരതയുള്ള AsyncLocalStorage API അവതരിപ്പിച്ചതോടെ, ഡെവലപ്പർമാർക്ക് ഇപ്പോൾ റിക്വസ്റ്റ്-സ്കോപ്പ്ഡ് വേരിയബിളുകൾ ഭംഗിയായും കാര്യക്ഷമമായും കൈകാര്യം ചെയ്യാൻ ശക്തവും ഇൻ-ബിൽറ്റുമായ ഒരു സംവിധാനമുണ്ട്. ഈ ഗൈഡ് നിങ്ങളെ ജാവാസ്ക്രിപ്റ്റ് അസിങ്ക് കോൺടെക്സ്റ്റിൻ്റെ ലോകത്തിലൂടെ ഒരു സമഗ്രമായ യാത്രയ്ക്ക് കൊണ്ടുപോകും, പ്രശ്നം വിശദീകരിക്കുകയും, പരിഹാരം അവതരിപ്പിക്കുകയും, ആഗോള ഉപയോക്താക്കൾക്കായി കൂടുതൽ സ്കെയിലബിൾ, പരിപാലിക്കാൻ എളുപ്പമുള്ളതും നിരീക്ഷിക്കാൻ കഴിയുന്നതുമായ ആപ്ലിക്കേഷനുകൾ നിർമ്മിക്കാൻ സഹായിക്കുന്നതിന് പ്രായോഗികവും യഥാർത്ഥവുമായ ഉദാഹരണങ്ങൾ നൽകുകയും ചെയ്യും.
പ്രധാന വെല്ലുവിളി: കൺകറൻ്റ്, അസിൻക്രണസ് ലോകത്തിലെ സ്റ്റേറ്റ്
പരിഹാരത്തെ പൂർണ്ണമായി വിലയിരുത്തുന്നതിന്, നമ്മൾ ആദ്യം പ്രശ്നത്തിൻ്റെ ആഴം മനസ്സിലാക്കണം. ഒരു Node.js സെർവർ ആയിരക്കണക്കിന് കൺകറൻ്റ് അഭ്യർത്ഥനകൾ കൈകാര്യം ചെയ്യുന്നു. അഭ്യർത്ഥന A വരുമ്പോൾ, Node.js അത് പ്രോസസ്സ് ചെയ്യാൻ തുടങ്ങിയേക്കാം, തുടർന്ന് ഒരു ഡാറ്റാബേസ് ക്വറി പൂർത്തിയാകാൻ കാത്തിരിക്കാനായി താൽക്കാലികമായി നിർത്താം. കാത്തിരിക്കുമ്പോൾ, അത് അഭ്യർത്ഥന B എടുത്ത് അതിൽ പ്രവർത്തിക്കാൻ തുടങ്ങുന്നു. അഭ്യർത്ഥന A-യുടെ ഡാറ്റാബേസ് ഫലം തിരികെ വരുമ്പോൾ, Node.js അതിൻ്റെ പ്രവർത്തനം പുനരാരംഭിക്കുന്നു. ഈ നിരന്തരമായ കോൺടെക്സ്റ്റ് സ്വിച്ചിംഗ് ആണ് അതിൻ്റെ പ്രകടനത്തിന് പിന്നിലെ മാന്ത്രികത, പക്ഷേ ഇത് പരമ്പരാഗത സ്റ്റേറ്റ് മാനേജ്മെൻ്റ് രീതികളെ തകിടം മറിക്കുന്നു.
എന്തുകൊണ്ടാണ് ഗ്ലോബൽ വേരിയബിളുകൾ പരാജയപ്പെടുന്നത്
ഒരു പുതിയ ഡെവലപ്പറുടെ ആദ്യത്തെ ചിന്ത ഒരു ഗ്ലോബൽ വേരിയബിൾ ഉപയോഗിക്കാമെന്നായിരിക്കും. ഉദാഹരണത്തിന്:
let currentUser; // ഒരു ഗ്ലോബൽ വേരിയബിൾ
// യൂസറിനെ സെറ്റ് ചെയ്യാനുള്ള മിഡിൽവെയർ
app.use((req, res, next) => {
currentUser = await getUserFromDb(req.headers.authorization);
next();
});
// ആപ്ലിക്കേഷനിലെ ഒരു സർവീസ് ഫംഗ്ഷൻ
function logActivity() {
console.log(`Activity for user: ${currentUser.id}`);
}
കൺകറൻ്റ് എൻവയോൺമെൻ്റിൽ ഇതൊരു വലിയ ഡിസൈൻ പിഴവാണ്. അഭ്യർത്ഥന A currentUser സെറ്റ് ചെയ്ത ശേഷം ഒരു അസിങ്ക് ഓപ്പറേഷനായി കാത്തിരിക്കുകയാണെങ്കിൽ, അഭ്യർത്ഥന B വന്ന് അഭ്യർത്ഥന A പൂർത്തിയാകുന്നതിന് മുമ്പ് currentUser ഓവർറൈറ്റ് ചെയ്തേക്കാം. അഭ്യർത്ഥന A പുനരാരംഭിക്കുമ്പോൾ, അത് അഭ്യർത്ഥന B-യിൽ നിന്നുള്ള ഡാറ്റ തെറ്റായി ഉപയോഗിക്കും. ഇത് പ്രവചനാതീതമായ ബഗുകൾ, ഡാറ്റാ നഷ്ടം, സുരക്ഷാ വീഴ്ചകൾ എന്നിവ സൃഷ്ടിക്കുന്നു. ഗ്ലോബൽ വേരിയബിളുകൾ റിക്വസ്റ്റ്-സേഫ് അല്ല.
പ്രോപ്പ് ഡ്രില്ലിംഗിൻ്റെ ബുദ്ധിമുട്ടുകൾ
കൂടുതൽ സാധാരണവും സുരക്ഷിതവുമായ ഒരു മാർഗ്ഗം "പ്രോപ്പ് ഡ്രില്ലിംഗ്" അല്ലെങ്കിൽ "പാരാമീറ്റർ പാസ്സിംഗ്" ആണ്. ഇതിന് ആവശ്യമുള്ള എല്ലാ ഫംഗ്ഷനിലേക്കും കോൺടെക്സ്റ്റ് ഒരു ആർഗ്യുമെൻ്റായി വ്യക്തമായി കൈമാറുന്നത് ഉൾപ്പെടുന്നു.
നമ്മുടെ ആപ്ലിക്കേഷനിലുടനീളം ലോഗിംഗിനായി ഒരു സവിശേഷമായ traceId-യും ഓതറൈസേഷനായി ഒരു user ഒബ്ജക്റ്റും ആവശ്യമാണെന്ന് സങ്കൽപ്പിക്കുക.
പ്രോപ്പ് ഡ്രില്ലിംഗിൻ്റെ ഉദാഹരണം:
// 1. എൻട്രി പോയിൻ്റ്: മിഡിൽവെയർ
app.use((req, res, next) => {
const traceId = generateTraceId();
const user = { id: 'user-123', locale: 'en-GB' };
const requestContext = { traceId, user };
processOrder(requestContext, req.body.orderId);
});
// 2. ബിസിനസ് ലോജിക് ലെയർ
function processOrder(context, orderId) {
log('Processing order', context);
const orderDetails = getOrderDetails(context, orderId);
// ... കൂടുതൽ ലോജിക്
}
// 3. ഡാറ്റ ആക്സസ് ലെയർ
function getOrderDetails(context, orderId) {
log(`Fetching order ${orderId}`, context);
return db.query('SELECT * FROM orders WHERE id = ?', orderId);
}
// 4. യൂട്ടിലിറ്റി ലെയർ
function log(message, context) {
console.log(`[${context.traceId}] [User: ${context.user.id}] - ${message}`);
}
ഇത് പ്രവർത്തിക്കുമെങ്കിലും കൺകറൻസി പ്രശ്നങ്ങളിൽ നിന്ന് സുരക്ഷിതമാണെങ്കിലും, ഇതിന് കാര്യമായ പോരായ്മകളുണ്ട്:
- കോഡിൻ്റെ സങ്കീർണ്ണത:
contextഒബ്ജക്റ്റ് നേരിട്ട് ഉപയോഗിക്കാത്ത ഫംഗ്ഷനുകളിലൂടെ പോലും എല്ലായിടത്തും പാസ് ചെയ്യപ്പെടുന്നു, കാരണം അവ വിളിക്കുന്ന ഫംഗ്ഷനുകളിലേക്ക് ഇത് കൈമാറേണ്ടതുണ്ട്. - കർശനമായ ബന്ധം: ഓരോ ഫംഗ്ഷൻ സിഗ്നേച്ചറും ഇപ്പോൾ
contextഒബ്ജക്റ്റിൻ്റെ ഘടനയുമായി ബന്ധിപ്പിച്ചിരിക്കുന്നു. നിങ്ങൾക്ക് കോൺടെക്സ്റ്റിലേക്ക് ഒരു പുതിയ ഡാറ്റ ചേർക്കണമെങ്കിൽ (ഉദാഹരണത്തിന്, ഒരു എ/ബി ടെസ്റ്റിംഗ് ഫ്ലാഗ്), നിങ്ങളുടെ കോഡ്ബേസിലുടനീളമുള്ള ഡസൻ കണക്കിന് ഫംഗ്ഷൻ സിഗ്നേച്ചറുകൾ മാറ്റേണ്ടി വന്നേക്കാം. - വായനാക്ഷമത കുറയുന്നു: കോൺടെക്സ്റ്റ് കൈമാറുന്നതിൻ്റെ ബോയിലർപ്ലേറ്റ് കാരണം ഒരു ഫംഗ്ഷൻ്റെ യഥാർത്ഥ ഉദ്ദേശ്യം മറയ്ക്കപ്പെടാം.
- പരിപാലന ഭാരം: റീഫാക്ടറിംഗ് ശ്രമകരവും പിശകുകൾക്ക് സാധ്യതയുള്ളതുമായ ഒരു പ്രക്രിയയായി മാറുന്നു.
നമുക്ക് ഇതിലും മികച്ച ഒരു മാർഗ്ഗം ആവശ്യമായിരുന്നു. വ്യക്തമായി കൈമാറാതെ തന്നെ, ഒരു അഭ്യർത്ഥനയുടെ അസിൻക്രണസ് കോൾ ശൃംഖലയിൽ എവിടെനിന്നും ആക്സസ് ചെയ്യാവുന്ന, അഭ്യർത്ഥന-നിർദ്ദിഷ്ട ഡാറ്റ സൂക്ഷിക്കുന്ന ഒരു "മാന്ത്രിക" കണ്ടെയ്നർ.
AsyncLocalStorage: ആധുനിക പരിഹാരം
Node.js v13.10.0 മുതൽ സ്ഥിരതയുള്ള ഒരു ഫീച്ചറായ AsyncLocalStorage ക്ലാസ്, ഈ പ്രശ്നത്തിനുള്ള ഔദ്യോഗിക മറുപടിയാണ്. ഒരു നിർദ്ദിഷ്ട എൻട്രി പോയിൻ്റിൽ നിന്ന് ആരംഭിച്ച അസിൻക്രണസ് പ്രവർത്തനങ്ങളുടെ മുഴുവൻ ശൃംഖലയിലും നിലനിൽക്കുന്ന ഒരു ഒറ്റപ്പെട്ട സ്റ്റോറേജ് കോൺടെക്സ്റ്റ് സൃഷ്ടിക്കാൻ ഇത് ഡെവലപ്പർമാരെ അനുവദിക്കുന്നു.
ജാവാസ്ക്രിപ്റ്റിൻ്റെ അസിൻക്രണസ്, ഇവൻ്റ്-ഡ്രിവൺ ലോകത്തിനായുള്ള "ത്രെഡ്-ലോക്കൽ സ്റ്റോറേജിൻ്റെ" ഒരു രൂപമായി നിങ്ങൾക്ക് ഇതിനെ കണക്കാക്കാം. നിങ്ങൾ ഒരു AsyncLocalStorage കോൺടെക്സ്റ്റിനുള്ളിൽ ഒരു പ്രവർത്തനം ആരംഭിക്കുമ്പോൾ, ആ പോയിൻ്റിൽ നിന്ന് വിളിക്കപ്പെടുന്ന ഏത് ഫംഗ്ഷനും—അത് സിൻക്രണസ്, കോൾബാക്ക്-ബേസ്ഡ്, അല്ലെങ്കിൽ പ്രോമിസ്-ബേസ്ഡ് ആകട്ടെ—ആ കോൺടെക്സ്റ്റിൽ സംഭരിച്ചിരിക്കുന്ന ഡാറ്റ ആക്സസ് ചെയ്യാൻ കഴിയും.
പ്രധാന API ആശയങ്ങൾ
API വളരെ ലളിതവും ശക്തവുമാണ്. ഇത് മൂന്ന് പ്രധാന മെത്തേഡുകളെ ചുറ്റിപ്പറ്റിയാണ് പ്രവർത്തിക്കുന്നത്:
new AsyncLocalStorage(): സ്റ്റോറിൻ്റെ ഒരു പുതിയ ഇൻസ്റ്റൻസ് ഉണ്ടാക്കുന്നു. സാധാരണയായി ഓരോ തരം കോൺടെക്സ്റ്റിനും (ഉദാഹരണത്തിന്, എല്ലാ HTTP അഭ്യർത്ഥനകൾക്കും ഒന്ന്) ഒരു ഇൻസ്റ്റൻസ് ഉണ്ടാക്കുകയും അത് നിങ്ങളുടെ ആപ്ലിക്കേഷനിലുടനീളം പങ്കുവെക്കുകയും ചെയ്യുന്നു.als.run(store, callback): ഇതാണ് പ്രധാന പ്രവർത്തനം. ഇത് ഒരു ഫംഗ്ഷൻ (callback) പ്രവർത്തിപ്പിക്കുകയും ഒരു പുതിയ അസിൻക്രണസ് കോൺടെക്സ്റ്റ് സ്ഥാപിക്കുകയും ചെയ്യുന്നു. ആദ്യത്തെ ആർഗ്യുമെൻ്റ്,store, ആ കോൺടെക്സ്റ്റിനുള്ളിൽ ലഭ്യമാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്ന ഡാറ്റയാണ്. അസിങ്ക് പ്രവർത്തനങ്ങൾ ഉൾപ്പെടെcallback-നുള്ളിൽ എക്സിക്യൂട്ട് ചെയ്യുന്ന ഏത് കോഡിനും ഈstore-ലേക്ക് ആക്സസ് ഉണ്ടായിരിക്കും.als.getStore(): നിലവിലെ കോൺടെക്സ്റ്റിൽ നിന്ന് ഡാറ്റ (store) വീണ്ടെടുക്കാൻ ഈ മെത്തേഡ് ഉപയോഗിക്കുന്നു.run()സ്ഥാപിച്ച ഒരു കോൺടെക്സ്റ്റിന് പുറത്ത് വിളിച്ചാൽ, ഇത്undefinedനൽകും.
പ്രായോഗിക നിർവ്വഹണം: ഒരു ഘട്ടം ഘട്ടമായുള്ള ഗൈഡ്
നമ്മുടെ മുൻപത്തെ പ്രോപ്പ്-ഡ്രില്ലിംഗ് ഉദാഹരണം AsyncLocalStorage ഉപയോഗിച്ച് റീഫാക്ടർ ചെയ്യാം. നമ്മൾ ഒരു സാധാരണ Express.js സെർവർ ഉപയോഗിക്കും, എന്നാൽ തത്വം ഏത് Node.js ഫ്രെയിംവർക്കിനും അല്ലെങ്കിൽ നേറ്റീവ് http മൊഡ്യൂളിനും ഒന്നുതന്നെയാണ്.
ഘട്ടം 1: ഒരു കേന്ദ്രീകൃത AsyncLocalStorage ഇൻസ്റ്റൻസ് ഉണ്ടാക്കുക
നിങ്ങളുടെ സ്റ്റോറിൻ്റെ ഒരൊറ്റ, ഷെയേർഡ് ഇൻസ്റ്റൻസ് ഉണ്ടാക്കി എക്സ്പോർട്ട് ചെയ്യുന്നത് ഒരു മികച്ച രീതിയാണ്, അതുവഴി അത് നിങ്ങളുടെ ആപ്ലിക്കേഷനിലുടനീളം ഉപയോഗിക്കാൻ കഴിയും. നമുക്ക് asyncContext.js എന്ന പേരിൽ ഒരു ഫയൽ ഉണ്ടാക്കാം.
// asyncContext.js
import { AsyncLocalStorage } from 'async_hooks';
export const requestContextStore = new AsyncLocalStorage();
ഘട്ടം 2: ഒരു മിഡിൽവെയർ ഉപയോഗിച്ച് കോൺടെക്സ്റ്റ് സ്ഥാപിക്കുക
ഒരു റിക്വസ്റ്റിൻ്റെ ജീവിതചക്രത്തിൻ്റെ തുടക്കത്തിൽ തന്നെ കോൺടെക്സ്റ്റ് ആരംഭിക്കുന്നതാണ് ഏറ്റവും അനുയോജ്യം. ഇതിനായി ഒരു മിഡിൽവെയർ തികച്ചും അനുയോജ്യമാണ്. നമ്മൾ നമ്മുടെ അഭ്യർത്ഥന-നിർദ്ദിഷ്ട ഡാറ്റ ജനറേറ്റ് ചെയ്യുകയും തുടർന്ന് ബാക്കിയുള്ള അഭ്യർത്ഥന കൈകാര്യം ചെയ്യൽ ലോജിക്ക് als.run()-നുള്ളിൽ ഉൾപ്പെടുത്തുകയും ചെയ്യും.
// server.js
import express from 'express';
import { requestContextStore } from './asyncContext.js';
import { v4 as uuidv4 } from 'uuid'; // ഒരു സവിശേഷമായ traceId ജനറേറ്റ് ചെയ്യാൻ
const app = express();
// മാന്ത്രിക മിഡിൽവെയർ
app.use((req, res, next) => {
const traceId = req.headers['x-request-id'] || uuidv4();
const user = { id: 'user-123', locale: 'en-GB' }; // ഒരു യഥാർത്ഥ ആപ്പിൽ, ഇത് ഒരു ഓതറൈസേഷൻ മിഡിൽവെയറിൽ നിന്ന് വരുന്നു
const store = { traceId, user };
// ഈ അഭ്യർത്ഥനയ്ക്കുള്ള കോൺടെക്സ്റ്റ് സ്ഥാപിക്കുക
requestContextStore.run(store, () => {
next();
});
});
// ... നിങ്ങളുടെ റൂട്ടുകളും മറ്റ് മിഡിൽവെയറുകളും ഇവിടെ വരുന്നു
ഈ മിഡിൽവെയറിൽ, ഓരോ ഇൻകമിംഗ് റിക്വസ്റ്റിനും, നമ്മൾ traceId-യും user-ഉം അടങ്ങുന്ന ഒരു store ഒബ്ജക്റ്റ് ഉണ്ടാക്കുന്നു. തുടർന്ന് നമ്മൾ requestContextStore.run(store, ...) വിളിക്കുന്നു. ഉള്ളിലുള്ള next() കോൾ ഉറപ്പാക്കുന്നത്, ഈ നിർദ്ദിഷ്ട അഭ്യർത്ഥനയ്ക്കുള്ള തുടർന്നുള്ള എല്ലാ മിഡിൽവെയറുകളും റൂട്ട് ഹാൻഡ്ലറുകളും ഈ പുതുതായി സൃഷ്ടിച്ച കോൺടെക്സ്റ്റിനുള്ളിൽ പ്രവർത്തിക്കുമെന്നാണ്.
ഘട്ടം 3: പ്രോപ്പ് ഡ്രില്ലിംഗ് ഇല്ലാതെ എവിടെ നിന്നും കോൺടെക്സ്റ്റ് ആക്സസ് ചെയ്യുക
ഇപ്പോൾ, നമ്മുടെ മറ്റ് മൊഡ്യൂളുകൾ വളരെ ലളിതമാക്കാം. അവയ്ക്ക് ഇനി ഒരു context പാരാമീറ്റർ ആവശ്യമില്ല. അവയ്ക്ക് നമ്മുടെ requestContextStore ഇമ്പോർട്ട് ചെയ്ത് getStore() വിളിച്ചാൽ മതി.
പുതുക്കിയ ലോഗിംഗ് യൂട്ടിലിറ്റി:
// logger.js
import { requestContextStore } from './asyncContext.js';
export function log(message) {
const context = requestContextStore.getStore();
if (context) {
const { traceId, user } = context;
console.log(`[${traceId}] [User: ${user.id}] - ${message}`);
} else {
// ഒരു റിക്വസ്റ്റ് കോൺടെക്സ്റ്റിന് പുറത്തുള്ള ലോഗുകൾക്കുള്ള ഫോൾബാക്ക്
console.log(`[NO_CONTEXT] - ${message}`);
}
}
പുതുക്കിയ ബിസിനസ്, ഡാറ്റ ലെയറുകൾ:
// orderService.js
import { log } from './logger.js';
import * as db from './database.js';
export function processOrder(orderId) {
log('Processing order'); // കോൺടെക്സ്റ്റ് ആവശ്യമില്ല!
const orderDetails = getOrderDetails(orderId);
// ... കൂടുതൽ ലോജിക്
}
function getOrderDetails(orderId) {
log(`Fetching order ${orderId}`); // ലോഗർ യാന്ത്രികമായി കോൺടെക്സ്റ്റ് എടുക്കും
return db.query('SELECT * FROM orders WHERE id = ?', orderId);
}
വ്യത്യാസം വളരെ വലുതാണ്. കോഡ് നാടകീയമായി വൃത്തിയുള്ളതും വായിക്കാൻ എളുപ്പമുള്ളതും കോൺടെക്സ്റ്റിൻ്റെ ഘടനയിൽ നിന്ന് പൂർണ്ണമായും വേർപെട്ടതുമാണ്. നമ്മുടെ ലോഗിംഗ് യൂട്ടിലിറ്റി, ബിസിനസ് ലോജിക്, ഡാറ്റാ ആക്സസ് ലെയറുകൾ ഇപ്പോൾ അവയുടെ നിർദ്ദിഷ്ട ജോലികളിൽ മാത്രം ശ്രദ്ധ കേന്ദ്രീകരിക്കുന്നു. എപ്പോഴെങ്കിലും നമ്മുടെ റിക്വസ്റ്റ് കോൺടെക്സ്റ്റിലേക്ക് ഒരു പുതിയ പ്രോപ്പർട്ടി ചേർക്കണമെങ്കിൽ, അത് സൃഷ്ടിക്കുന്ന മിഡിൽവെയറിൽ മാത്രം മാറ്റം വരുത്തിയാൽ മതി. മറ്റ് ഫംഗ്ഷൻ സിഗ്നേച്ചറുകളൊന്നും മാറ്റേണ്ടതില്ല.
വിപുലമായ ഉപയോഗങ്ങളും ഒരു ആഗോള കാഴ്ചപ്പാടും
റിക്വസ്റ്റ്-സ്കോപ്പ്ഡ് കോൺടെക്സ്റ്റ് ലോഗിംഗിന് വേണ്ടി മാത്രമല്ല. സങ്കീർണ്ണവും ആഗോളവുമായ ആപ്ലിക്കേഷനുകൾ നിർമ്മിക്കുന്നതിന് അത്യാവശ്യമായ പലതരം ശക്തമായ പാറ്റേണുകൾ ഇത് സാധ്യമാക്കുന്നു.
1. ഡിസ്ട്രിബ്യൂട്ടഡ് ട്രെയ്സിംഗും ഒബ്സെർവബിലിറ്റിയും
ഒരു മൈക്രോസർവീസസ് ആർക്കിടെക്ചറിൽ, ഒരൊറ്റ ഉപയോക്തൃ പ്രവർത്തനം ഒന്നിലധികം സേവനങ്ങളിലുടനീളം അഭ്യർത്ഥനകളുടെ ഒരു ശൃംഖലയ്ക്ക് കാരണമായേക്കാം. പ്രശ്നങ്ങൾ പരിഹരിക്കുന്നതിന്, ഈ മുഴുവൻ യാത്രയും നിങ്ങൾക്ക് കണ്ടെത്താൻ കഴിയണം. ആധുനിക ട്രെയ്സിംഗിൻ്റെ അടിസ്ഥാന ശിലയാണ് AsyncLocalStorage. നിങ്ങളുടെ API ഗേറ്റ്വേയിലേക്കുള്ള ഒരു ഇൻകമിംഗ് അഭ്യർത്ഥനയ്ക്ക് ഒരു സവിശേഷമായ traceId നൽകാം. ഈ ഐഡി അസിങ്ക് കോൺടെക്സ്റ്റിൽ സംഭരിക്കുകയും താഴെയുള്ള സേവനങ്ങളിലേക്കുള്ള ഏതൊരു ഔട്ട്ബൗണ്ട് API കോളുകളിലും (ഉദാഹരണത്തിന്, ഒരു HTTP ഹെഡറായി) യാന്ത്രികമായി ഉൾപ്പെടുത്തുകയും ചെയ്യുന്നു. ഓരോ സേവനവും ഇതുതന്നെ ചെയ്യുന്നു, കോൺടെക്സ്റ്റ് പ്രചരിപ്പിക്കുന്നു. കേന്ദ്രീകൃത ലോഗിംഗ് പ്ലാറ്റ്ഫോമുകൾക്ക് ഈ ലോഗുകൾ ഉപയോഗിച്ച് നിങ്ങളുടെ മുഴുവൻ സിസ്റ്റത്തിലുടനീളമുള്ള ഒരു അഭ്യർത്ഥനയുടെ തുടക്കം മുതൽ ഒടുക്കം വരെയുള്ള ഒഴുക്ക് പുനർനിർമ്മിക്കാൻ കഴിയും.
2. ഇൻ്റർനാഷണലൈസേഷൻ (i18n), ലോക്കലൈസേഷൻ (l10n)
ഒരു ആഗോള ആപ്ലിക്കേഷന്, തീയതികൾ, സമയങ്ങൾ, നമ്പറുകൾ, കറൻസികൾ എന്നിവ ഉപയോക്താവിൻ്റെ പ്രാദേശിക ഫോർമാറ്റിൽ അവതരിപ്പിക്കുന്നത് നിർണായകമാണ്. ഉപയോക്താവിൻ്റെ അഭ്യർത്ഥന ഹെഡറുകളിൽ നിന്നോ ഉപയോക്തൃ പ്രൊഫൈലിൽ നിന്നോ അവരുടെ ലൊക്കേൽ (ഉദാ. 'fr-FR', 'ja-JP', 'en-US') അസിങ്ക് കോൺടെക്സ്റ്റിൽ സംഭരിക്കാം.
// കറൻസി ഫോർമാറ്റ് ചെയ്യുന്നതിനുള്ള ഒരു യൂട്ടിലിറ്റി
import { requestContextStore } from './asyncContext.js';
function formatCurrency(amount, currencyCode) {
const context = requestContextStore.getStore();
const locale = context?.user?.locale || 'en-US'; // ഒരു ഡിഫോൾട്ടിലേക്ക് ഫോൾബാക്ക് ചെയ്യുക
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currencyCode,
}).format(amount);
}
// ആപ്പിൻ്റെ ഉള്ളിൽ ഉപയോഗം
const priceString = formatCurrency(199.99, 'EUR'); // ഉപയോക്താവിൻ്റെ ലൊക്കേൽ യാന്ത്രികമായി ഉപയോഗിക്കുന്നു
ഇത് locale വേരിയബിൾ എല്ലായിടത്തും കൈമാറാതെ തന്നെ സ്ഥിരതയുള്ള ഒരു ഉപയോക്തൃ അനുഭവം ഉറപ്പാക്കുന്നു.
3. ഡാറ്റാബേസ് ട്രാൻസാക്ഷൻ മാനേജ്മെൻ്റ്
ഒരൊറ്റ റിക്വസ്റ്റിന് ഒന്നിലധികം ഡാറ്റാബേസ് റൈറ്റുകൾ നടത്തേണ്ടിവരുമ്പോൾ, അവയെല്ലാം ഒരുമിച്ച് വിജയിക്കുകയോ പരാജയപ്പെടുകയോ ചെയ്യേണ്ടിവരുമ്പോൾ, നിങ്ങൾക്ക് ഒരു ട്രാൻസാക്ഷൻ ആവശ്യമാണ്. ഒരു റിക്വസ്റ്റ് ഹാൻഡ്ലറിൻ്റെ തുടക്കത്തിൽ നിങ്ങൾക്ക് ഒരു ട്രാൻസാക്ഷൻ ആരംഭിക്കാം, ട്രാൻസാക്ഷൻ ക്ലയിൻ്റ് അസിങ്ക് കോൺടെക്സ്റ്റിൽ സംഭരിക്കാം, തുടർന്ന് ആ അഭ്യർത്ഥനയ്ക്കുള്ളിലെ തുടർന്നുള്ള എല്ലാ ഡാറ്റാബേസ് കോളുകളും ഒരേ ട്രാൻസാക്ഷൻ ക്ലയിൻ്റ് ഉപയോഗിക്കും. ഹാൻഡ്ലറിൻ്റെ അവസാനം, ഫലത്തെ അടിസ്ഥാനമാക്കി നിങ്ങൾക്ക് ട്രാൻസാക്ഷൻ കമ്മിറ്റ് ചെയ്യുകയോ റോൾബാക്ക് ചെയ്യുകയോ ചെയ്യാം.
4. ഫീച്ചർ ടോഗ്ലിംഗും എ/ബി ടെസ്റ്റിംഗും
ഒരു റിക്വസ്റ്റിൻ്റെ തുടക്കത്തിൽ ഒരു ഉപയോക്താവ് ഏത് ഫീച്ചർ ഫ്ലാഗുകളിലോ എ/ബി ടെസ്റ്റ് ഗ്രൂപ്പുകളിലോ ഉൾപ്പെടുന്നുവെന്ന് നിങ്ങൾക്ക് നിർണ്ണയിക്കാനും ഈ വിവരങ്ങൾ കോൺടെക്സ്റ്റിൽ സംഭരിക്കാനും കഴിയും. നിങ്ങളുടെ ആപ്ലിക്കേഷൻ്റെ വിവിധ ഭാഗങ്ങൾക്ക്, API ലെയർ മുതൽ റെൻഡറിംഗ് ലെയർ വരെ, ഏത് ഫീച്ചർ പതിപ്പ് എക്സിക്യൂട്ട് ചെയ്യണമെന്നോ ഏത് UI പ്രദർശിപ്പിക്കണമെന്നോ തീരുമാനിക്കാൻ കോൺടെക്സ്റ്റ് പരിശോധിക്കാം, ഇത് സങ്കീർണ്ണമായ പാരാമീറ്റർ പാസിംഗ് ഇല്ലാതെ ഒരു വ്യക്തിഗത അനുഭവം സൃഷ്ടിക്കുന്നു.
പ്രകടന പരിഗണനകളും മികച്ച രീതികളും
ഒരു സാധാരണ ചോദ്യമാണ്: ഇതിൻ്റെ പ്രകടന ഭാരം എന്താണ്? Node.js കോർ ടീം AsyncLocalStorage വളരെ കാര്യക്ഷമമാക്കുന്നതിന് കാര്യമായ പരിശ്രമം നടത്തിയിട്ടുണ്ട്. ഇത് C++ തലത്തിലുള്ള async_hooks API-യുടെ മുകളിലാണ് നിർമ്മിച്ചിരിക്കുന്നത്, കൂടാതെ V8 ജാവാസ്ക്രിപ്റ്റ് എഞ്ചിനുമായി ആഴത്തിൽ സംയോജിപ്പിച്ചിരിക്കുന്നു. ബഹുഭൂരിപക്ഷം വെബ് ആപ്ലിക്കേഷനുകൾക്കും, പ്രകടനത്തിലെ ആഘാതം നിസ്സാരമാണ്, കോഡ് ഗുണമേന്മയിലും പരിപാലനക്ഷമതയിലുമുള്ള വലിയ നേട്ടങ്ങളാൽ ഇത് മറികടക്കുന്നു.
ഇത് ഫലപ്രദമായി ഉപയോഗിക്കുന്നതിന്, ഈ മികച്ച രീതികൾ പിന്തുടരുക:
- ഒരു സിംഗിൾട്ടൺ ഇൻസ്റ്റൻസ് ഉപയോഗിക്കുക: നമ്മുടെ ഉദാഹരണത്തിൽ കാണിച്ചതുപോലെ, സ്ഥിരത ഉറപ്പാക്കാൻ നിങ്ങളുടെ റിക്വസ്റ്റ് കോൺടെക്സ്റ്റിനായി
AsyncLocalStorage-ൻ്റെ ഒരൊറ്റ, എക്സ്പോർട്ട് ചെയ്ത ഇൻസ്റ്റൻസ് ഉണ്ടാക്കുക. - തുടക്കത്തിൽ തന്നെ കോൺടെക്സ്റ്റ് സ്ഥാപിക്കുക:
als.run()വിളിക്കാൻ എല്ലായ്പ്പോഴും ഒരു ടോപ്പ്-ലെവൽ മിഡിൽവെയറോ അല്ലെങ്കിൽ ഒരു റിക്വസ്റ്റ് ഹാൻഡ്ലറിൻ്റെ തുടക്കമോ ഉപയോഗിക്കുക. ഇത് നിങ്ങളുടെ കോൺടെക്സ്റ്റിന് വ്യക്തവും പ്രവചിക്കാവുന്നതുമായ ഒരു അതിർത്തി സൃഷ്ടിക്കുന്നു. - സ്റ്റോറിനെ മാറ്റമില്ലാത്തതായി കണക്കാക്കുക: സ്റ്റോർ ഒബ്ജക്റ്റ് തന്നെ മ്യൂട്ടബിൾ ആണെങ്കിലും, അതിനെ ഇമ്മ്യൂട്ടബിൾ ആയി കണക്കാക്കുന്നത് ഒരു നല്ല പരിശീലനമാണ്. അഭ്യർത്ഥനയുടെ മധ്യത്തിൽ ഡാറ്റ ചേർക്കണമെങ്കിൽ, മറ്റൊരു
run()കോൾ ഉപയോഗിച്ച് ഒരു നെസ്റ്റഡ് കോൺടെക്സ്റ്റ് ഉണ്ടാക്കുന്നത് പലപ്പോഴും വൃത്തിയുള്ളതാണ്, എന്നിരുന്നാലും ഇത് കൂടുതൽ വിപുലമായ ഒരു പാറ്റേണാണ്. - കോൺടെക്സ്റ്റ് ഇല്ലാത്ത സാഹചര്യങ്ങൾ കൈകാര്യം ചെയ്യുക: നമ്മുടെ ലോഗറിൽ കാണിച്ചതുപോലെ,
getStore()undefinedനൽകുന്നുണ്ടോ എന്ന് നിങ്ങളുടെ യൂട്ടിലിറ്റികൾ എപ്പോഴും പരിശോധിക്കണം. ഇത് പശ്ചാത്തല സ്ക്രിപ്റ്റുകളിലോ ആപ്ലിക്കേഷൻ സ്റ്റാർട്ടപ്പ് സമയത്തോ പോലുള്ള ഒരു റിക്വസ്റ്റ് കോൺടെക്സ്റ്റിന് പുറത്ത് പ്രവർത്തിക്കുമ്പോൾ അവ ഭംഗിയായി പ്രവർത്തിക്കാൻ അനുവദിക്കുന്നു. - എറർ ഹാൻഡ്ലിംഗ് തടസ്സമില്ലാതെ പ്രവർത്തിക്കുന്നു: അസിങ്ക് കോൺടെക്സ്റ്റ്
Promiseശൃംഖലകൾ,.then()/.catch()/.finally()ബ്ലോക്കുകൾ,try/catchഉള്ളasync/awaitഎന്നിവയിലൂടെ ശരിയായി പ്രചരിക്കുന്നു. നിങ്ങൾ പ്രത്യേകമായി ഒന്നും ചെയ്യേണ്ടതില്ല; ഒരു പിശക് സംഭവിച്ചാൽ, നിങ്ങളുടെ എറർ ഹാൻഡ്ലിംഗ് ലോജിക്കിൽ കോൺടെക്സ്റ്റ് ലഭ്യമായിരിക്കും.
ഉപസംഹാരം: Node.js ആപ്ലിക്കേഷനുകൾക്ക് ഒരു പുതിയ യുഗം
AsyncLocalStorage ഒരു സൗകര്യപ്രദമായ യൂട്ടിലിറ്റി എന്നതിലുപരി; സെർവർ-സൈഡ് ജാവാസ്ക്രിപ്റ്റിലെ സ്റ്റേറ്റ് മാനേജ്മെൻ്റിനുള്ള ഒരു മാതൃകാപരമായ മാറ്റത്തെ ഇത് പ്രതിനിധീകരിക്കുന്നു. വളരെ ഉയർന്ന കൺകറൻസി ഉള്ള ഒരു പരിതസ്ഥിതിയിൽ റിക്വസ്റ്റ്-സ്കോപ്പ്ഡ് കോൺടെക്സ്റ്റ് കൈകാര്യം ചെയ്യുന്നതിനുള്ള ദീർഘകാല പ്രശ്നത്തിന് ഇത് വൃത്തിയുള്ളതും ശക്തവും പ്രകടനക്ഷമവുമായ ഒരു പരിഹാരം നൽകുന്നു.
ഈ API സ്വീകരിക്കുന്നതിലൂടെ, നിങ്ങൾക്ക് സാധിക്കുന്നത്:
- പ്രോപ്പ് ഡ്രില്ലിംഗ് ഒഴിവാക്കുക: വൃത്തിയുള്ളതും കൂടുതൽ ശ്രദ്ധ കേന്ദ്രീകരിക്കുന്നതുമായ ഫംഗ്ഷനുകൾ എഴുതുക.
- നിങ്ങളുടെ മൊഡ്യൂളുകളെ വേർതിരിക്കുക: ആശ്രിതത്വം കുറയ്ക്കുകയും നിങ്ങളുടെ കോഡ് റീഫാക്ടർ ചെയ്യാനും ടെസ്റ്റ് ചെയ്യാനും എളുപ്പമാക്കുകയും ചെയ്യുക.
- നിരീക്ഷണക്ഷമത വർദ്ധിപ്പിക്കുക: ശക്തമായ ഡിസ്ട്രിബ്യൂട്ടഡ് ട്രെയ്സിംഗും കോൺടെക്സ്റ്റൽ ലോഗിംഗും എളുപ്പത്തിൽ നടപ്പിലാക്കുക.
- സങ്കീർണ്ണമായ ഫീച്ചറുകൾ നിർമ്മിക്കുക: ട്രാൻസാക്ഷൻ മാനേജ്മെൻ്റ്, ഇൻ്റർനാഷണലൈസേഷൻ തുടങ്ങിയ സങ്കീർണ്ണമായ പാറ്റേണുകൾ ലളിതമാക്കുക.
Node.js-ൽ ആധുനികവും, സ്കെയിലബിളും, ആഗോളതലത്തിൽ ശ്രദ്ധിക്കുന്നതുമായ ആപ്ലിക്കേഷനുകൾ നിർമ്മിക്കുന്ന ഡെവലപ്പർമാർക്ക്, അസിങ്ക് കോൺടെക്സ്റ്റിൽ വൈദഗ്ദ്ധ്യം നേടുന്നത് ഇപ്പോൾ ഒരു ഓപ്ഷനല്ല—അതൊരു അത്യന്താപേക്ഷിതമായ കഴിവാണ്. കാലഹരണപ്പെട്ട പാറ്റേണുകൾക്കപ്പുറം നീങ്ങുകയും AsyncLocalStorage സ്വീകരിക്കുകയും ചെയ്യുന്നതിലൂടെ, നിങ്ങൾക്ക് കൂടുതൽ കാര്യക്ഷമമായ കോഡ് എഴുതാൻ കഴിയും, അത് അഗാധമായി കൂടുതൽ ഗംഭീരവും പരിപാലിക്കാൻ എളുപ്പമുള്ളതുമാണ്.